using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.DirectX.Direct3D;
using System.Drawing;
using TgcViewer;
using Microsoft.DirectX;
using AlumnoEjemplos.TP3D.Collider;

namespace AlumnoEjemplos.TP3D
{

    class Terreno : Plataforma, IDisposable
    {
        static readonly Material DEFAULT_MATERIAL = new Material();
        protected Texture terrainTexture = null;
        protected VertexBuffer vbTerrain = null;
        int[,] heightmapData = null;
        int totalVertices = 0;
        private Vector3 _center;
        private float _mapwidth;
        private float _maplength;

        private float _scaleXZ;
        private float _scaleY;

        /// <summary>
        /// Crea la malla de un terreno en base a un Heightmap
        /// </summary>
        /// <param name="heightmapPath">Imagen de Heightmap</param>
        /// <param name="scaleXZ">Escala para los ejes X y Z</param>
        /// <param name="scaleY">Escala para el eje Y</param>
        /// <param name="center">Centro de la malla del terreno</param>
        public Terreno(string heightmapPath, String mapPath, float scaleXZ, float scaleY, Vector3 center)
        {
            this.loadHeightmap(heightmapPath, scaleXZ, scaleY, center);
            this.loadTexture(mapPath);
        }

        public void MoveX(float ammount)
        {
            // Lo que es el uso de interfaces no?
        }

        private void loadHeightmap(string heightmapPath, float scaleXZ, float scaleY, Vector3 center)
        {
            Device d3dDevice = GuiController.Instance.D3dDevice;

            //Dispose de VertexBuffer anterior, si habia
            if (vbTerrain != null && !vbTerrain.Disposed)
            {
                vbTerrain.Dispose();
            }

            this._scaleXZ = scaleXZ;
            this._scaleY = scaleY;

            //cargar heightmap
            heightmapData = loadHeightMap(d3dDevice, heightmapPath);
            float width = (float)heightmapData.GetLength(0);
            float length = (float)heightmapData.GetLength(1);

            //Crear vertexBuffer
            totalVertices = 2 * 3 * (heightmapData.GetLength(0) - 1) * (heightmapData.GetLength(1) - 1);
            vbTerrain = new VertexBuffer(typeof(CustomVertex.PositionTextured), totalVertices, d3dDevice, Usage.Dynamic | Usage.WriteOnly, CustomVertex.PositionTextured.Format, Pool.Default);

            //Cargar vertices
            int dataIdx = 0;
            CustomVertex.PositionTextured[] data = new CustomVertex.PositionTextured[totalVertices];

            _mapwidth = width * scaleXZ;
            _maplength = length * scaleXZ;

            center.X = center.X * scaleXZ - (width / 2) * scaleXZ;
            center.Y = center.Y * scaleY;
            center.Z = center.Z * scaleXZ - (length / 2) * scaleXZ;


            _center = center;

            for (int i = 0; i < width - 1; i++)
            {
                for (int j = 0; j < length - 1; j++)
                {
                    Vector3 v1 = new Vector3(center.X + i * scaleXZ, center.Y + heightmapData[i, j] * scaleY, center.Z + j * scaleXZ);
                    Vector3 v2 = new Vector3(center.X + i * scaleXZ, center.Y + heightmapData[i, j + 1] * scaleY, center.Z + (j + 1) * scaleXZ);
                    Vector3 v3 = new Vector3(center.X + (i + 1) * scaleXZ, center.Y + heightmapData[i + 1, j] * scaleY, center.Z + j * scaleXZ);
                    Vector3 v4 = new Vector3(center.X + (i + 1) * scaleXZ, center.Y + heightmapData[i + 1, j + 1] * scaleY, center.Z + (j + 1) * scaleXZ);


                    Vector2 t1 = new Vector2(i / width, j / length);
                    Vector2 t2 = new Vector2(i / width, (j + 1) / length);
                    Vector2 t3 = new Vector2((i + 1) / width, j / length);
                    Vector2 t4 = new Vector2((i + 1) / width, (j + 1) / length);

                    //vertex 1
                    data[dataIdx] = new CustomVertex.PositionTextured(v1, t1.X, t1.Y);
                    data[dataIdx + 1] = new CustomVertex.PositionTextured(v2, t2.X, t2.Y);
                    data[dataIdx + 2] = new CustomVertex.PositionTextured(v4, t4.X, t4.Y);

                    //vertex 2
                    data[dataIdx + 3] = new CustomVertex.PositionTextured(v1, t1.X, t1.Y);
                    data[dataIdx + 4] = new CustomVertex.PositionTextured(v3, t3.X, t3.Y);
                    data[dataIdx + 5] = new CustomVertex.PositionTextured(v4, t4.X, t4.Y);

                    dataIdx += 6;
                }
            }

            vbTerrain.SetData(data, 0, LockFlags.None);
        }

        public void loadTexture(string path)
        {
            //Dispose textura anterior, si habia
            if (terrainTexture != null && !terrainTexture.Disposed)
            {
                terrainTexture.Dispose();
            }

            Device d3dDevice = GuiController.Instance.D3dDevice;

            using (Bitmap b = (Bitmap)Bitmap.FromFile(path))
            {
                //b.RotateFlip(RotateFlipType.Rotate90FlipX);
                terrainTexture = Texture.FromBitmap(d3dDevice, b, Usage.None, Pool.Managed);
            }
        }

        private int[,] loadHeightMap(Device d3dDevice, string path)
        {
            using (Bitmap bitmap = (Bitmap)Bitmap.FromFile(path))
            {
                int width = bitmap.Size.Width;
                int height = bitmap.Size.Height;
                int[,] heightmap = new int[width, height];
                for (int j = 0; j < height; j++)
                {
                    for (int i = 0; i < width; i++)
                    {
                        Color pixel = bitmap.GetPixel(i, j);
                        heightmap[i, j] = (int)GetGrayScaleIntensity(pixel);
                    }
                }
                return heightmap;
            }
        }

        private float GetGrayScaleIntensity(Color pixel)
        {
            return pixel.R * 0.299f + pixel.G * 0.587f + pixel.B * 0.114f;
        }

        public void render()
        {
            Device d3dDevice = GuiController.Instance.D3dDevice;
            d3dDevice.Transform.World = Matrix.Identity;

            d3dDevice.SetTexture(0, terrainTexture);
            d3dDevice.SetTexture(1, null);
            d3dDevice.Material = DEFAULT_MATERIAL;

            d3dDevice.VertexFormat = CustomVertex.PositionTextured.Format;
            d3dDevice.SetStreamSource(0, vbTerrain, 0);
            d3dDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, totalVertices / 3);
        }

        #region IDisposable Members

        public void Dispose()
        {

        }

        #endregion

        #region Plataforma Members

        public void AddGuest(Placeable mario)
        {
            //Do nothing
        }


        public Vector3 MovimientoSegun(Vector3 velocidad, Vector3 pos)
        {
            return velocidad;
        }

        public float GetAltura(Vector3 elementPosition)
        {
            // the first thing we need to do is figure out where on the heightmap
            // "position" is. This'll make the math much simpler later.
            Vector3 positionOnHeightmap = elementPosition - this.Position;

            // we'll use integer division to figure out where in the "heights" array
            // positionOnHeightmap is. Remember that integer division always rounds
            // down, so that the result of these divisions is the indices of the "upper
            // left" of the 4 corners of that cell.
            int left, top;
            left = (int)(positionOnHeightmap.X + (this._maplength/2)) / (int)_scaleXZ;
            top = (int)(positionOnHeightmap.Z + (this._mapwidth/2)) / (int)_scaleXZ;

            // next, we'll use modulus to find out how far away we are from the upper
            // left corner of the cell. Mod will give us a value from 0 to terrainScale,
            // which we then divide by terrainScale to normalize 0 to 1.
            float xNormalized = (positionOnHeightmap.X % _scaleXZ) / _scaleXZ;
            float zNormalized = (positionOnHeightmap.Z % _scaleXZ) / _scaleXZ;

            // Now that we've calculated the indices of the corners of our cell, and
            // where we are in that cell, we'll use bilinear interpolation to calculuate
            // our height. This process is best explained with a diagram, so please see
            // the accompanying doc for more information.
            // First, calculate the heights on the bottom and top edge of our cell by
            // interpolating from the left and right sides.
            float topHeight = MathHelper.Lerp(
                this.heightmapData[left, top],
                this.heightmapData[left + 1, top],
                xNormalized);

            float bottomHeight = MathHelper.Lerp(
                this.heightmapData[left, top + 1],
                this.heightmapData[left + 1, top + 1],
                xNormalized);

            // next, interpolate between those two values to calculate the height at our
            // position.
            return MathHelper.Lerp(topHeight, bottomHeight, zNormalized) + this.Position.Y;
        }

        #endregion

        #region Plataforma Members


        public Vector3 Position
        {
            get
            {
                Vector3 pos = this._center;
                pos.X += _mapwidth / 2;
                pos.Z += _maplength / 2;
                return pos;
            }
            set
            {
                Vector3 pos = value;
                pos.X -= _mapwidth/2;
                pos.Z -= _maplength/2;
                this._center = pos;
            }
        }

        #endregion

        public void doSomethingToMassive(Placeable p)
        {

        }
        #region Plataforma Members


        public void RemoveGuest(Mario mario)
        {
            //Nothing
        }

        #endregion

        #region Plataforma Members


        public bool EstasEn(float x, float z)
        {
            return true;
        }

        #endregion
    }
}
